package org.web.accesscontrollist.server.biz.impl;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.web.accesscontrollist.sdk.biz.AclResourceDetailBiz;
import org.web.accesscontrollist.sdk.dto.*;
import org.web.accesscontrollist.sdk.dto.query.QueryAclResourceDetailRequest;
import org.web.accesscontrollist.sdk.dto.redundancy.DetailRedundancyDTO;
import org.web.accesscontrollist.sdk.helper.AclHelper;
import org.web.accesscontrollist.server.domain.*;
import org.web.accesscontrollist.server.domain.query.*;
import org.web.accesscontrollist.server.service.*;
import org.web.base.domain.*;
import org.web.base.domain.exception.ResultMessageEnum;
import org.web.base.domain.exception.ServiceException;
import org.web.base.domain.helper.ResultHelper;
import org.web.base.domain.helper.ServiceExceptionHelper;
import org.web.base.domain.helper.ValidationHelper;
import org.web.base.helper.EnumHelper;

//import jakarta.annotation.Resource;
import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service("aclResourceDetailBiz")
public class AclResourceDetailBizImpl implements AclResourceDetailBiz {

    private static final Logger logger = Logger.getLogger(AclResourceDetailBizImpl.class);

    private static final Set<String> querySimpleSet = new HashSet<String>() {{
        add("acl_resource_detail_id");
        add("business_line");
        add("type_name");
        add("detail_name");
        add("create_time");
        add("is_delete");
    }};

    private static final Set<String> queryTypeNameSet = new HashSet<String>() {{
        add("acl_resource_type_id");
        add("business_line");
        add("type_name");
    }};

    @Resource
    private AclResourceDetailService aclResourceDetailService;

    @Resource
    private AclResourceTypeService aclResourceTypeService;

    @Resource
    private AclResourceUserBindingService aclResourceUserBindingService;

    @Resource
    private SaveBatchBizImpl saveBatchBiz;

    @Override
    public ResultDO<List<AclResourceDetailDTO>> saveAclResourceDetailBatch(AclResourceDetailBatchDTO aclResourceDetailBatchDTO) {
        ResultDO<List<AclResourceDetailDTO>> resultDO = new ResultDO<>();
        OperatorDTO operatorDTO = null;
        try {
            AclHelper.checkSaveAclDetailBatchDTO(aclResourceDetailBatchDTO, UpdateGroup.class);
            resultDO.setDomain(aclResourceDetailBatchDTO.getAclResourceDetailDTOList());
            // 判断业务线businessLine是否存在, 则校验权限。
            operatorDTO = aclResourceDetailBatchDTO.getOperatorDTO();
            // aclBusinessLineServiceBiz.judgeOperatorOrBuildDefaultBusinessLine(operatorDTO);

            List<String> typeNameList = new ArrayList<>();
            List<String> detailNameList = new ArrayList<>();

            // 根据typeNameList，detailNameList和businessLine,通过一次查询，查出数据库所有的数据。不在循环中访问数据库，减少数据库操作次数。
            Map<String, AclResourceDetailDTO> resourceDetailDTOMap = new HashMap<>();
            for (AclResourceDetailDTO aclResourceDetailDTO : aclResourceDetailBatchDTO.getAclResourceDetailDTOList()) {
                AclHelper.addIfNotDuplicate(typeNameList, aclResourceDetailDTO.getTypeName());
                AclHelper.addIfNotDuplicate(detailNameList, aclResourceDetailDTO.getDetailName());
                String keyName = AclHelper.buildUniqueKeyByAttributes(aclResourceDetailDTO.getBusinessLine(), aclResourceDetailDTO.getTypeName(), aclResourceDetailDTO.getDetailName());
                resourceDetailDTOMap.put(keyName, aclResourceDetailDTO);
            }

            List<String> errorList = new ArrayList<>();
            // (1)数据依赖关系校验，ResourceDetail依赖 ResourceType,根据businessLine、typeName校验ResourceType是否存在。提交批次中的businessLine、typeName在acl_resource_type表都应该存在。
            setAclResourceTypeIdAndCheckTypeNameExist(typeNameList, errorList, aclResourceDetailBatchDTO);

            QueryAclResourceDetail queryAclResourceDetail = new QueryAclResourceDetail();
            queryAclResourceDetail.setAllRecords();
            queryAclResourceDetail.setBusinessLine(aclResourceDetailBatchDTO.getOperatorDTO().getBusinessLine());
            queryAclResourceDetail.setTypeNameList(typeNameList);
            queryAclResourceDetail.setDetailNameList(detailNameList);
            queryAclResourceDetail.setSpecifiedAttributeSet(querySimpleSet);
            List<AclResourceDetailDO> selectListFromDB = aclResourceDetailService.selectAclResourceDetailList(queryAclResourceDetail);

            Map<String, AclResourceDetailDO> mapFromDB = new HashMap<>();
            for (AclResourceDetailDO aclResourceDetailDO : selectListFromDB) {
                String keyName = AclHelper.buildUniqueKeyByAttributes(aclResourceDetailDO.getBusinessLine(), aclResourceDetailDO.getTypeName(), aclResourceDetailDO.getDetailName());
                mapFromDB.put(keyName, aclResourceDetailDO);
            }

            List<AclResourceDetailDO> insertList = new ArrayList<>();// 新增数据队列
            List<AclResourceDetailDO> updateList = new ArrayList<>();// 修改数据队列

            int count = 0;
            for (AclResourceDetailDTO aclResourceDetailDTO : aclResourceDetailBatchDTO.getAclResourceDetailDTOList()) {
                ++count;
                String keyName = AclHelper.buildUniqueKeyByAttributes(aclResourceDetailDTO.getBusinessLine(), aclResourceDetailDTO.getTypeName(), aclResourceDetailDTO.getDetailName());
                AclResourceDetailDO aclResourceDetailDOFromDB = mapFromDB.get(keyName);
                if (aclResourceDetailDOFromDB == null) {
                    AclResourceDetailDO insertAclResourceDetailDO = new AclResourceDetailDO();
                    BeanUtils.copyProperties(insertAclResourceDetailDO, aclResourceDetailDTO);
                    // insert的场合，校验下数据合法性。
                    AclHelper.validate(count, errorList, AclResourceDetailDTO.class, aclResourceDetailDTO, InsertGroup.class);

                    insertList.add(insertAclResourceDetailDO);
                } else {
                    AclResourceDetailDO updateAclResourceDetailDO = new AclResourceDetailDO();
                    BeanUtils.copyProperties(updateAclResourceDetailDO, aclResourceDetailDTO);
                    // update的场合，不需要校验数据合法性。原因是前面的 AclHelper.checkSaveAclDetailBatchDTO(aclResourceTypeBatchDTO)已经校验。
                    updateAclResourceDetailDO.setAclResourceDetailId(aclResourceDetailDOFromDB.getAclResourceDetailId());
                    updateList.add(updateAclResourceDetailDO);
                }
            }

            if (!errorList.isEmpty()) {
                ServiceException serviceException = ServiceExceptionHelper.buildServiceException(ResultMessageEnum.PARAM_INVALID);
                serviceException.setErrorList(errorList);
                throw serviceException;
            }
            // 最后进行数据更新。
            saveBatchBiz.saveBatch(insertList, updateList, mapFromDB, resourceDetailDTOMap);

        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            resultDO = ResultHelper.buildResultDOByException(e, resultDO.getDomain());
        } finally {
            if (operatorDTO != null) {
                operatorDTO.setAccessKey(null);
            }
        }
        return resultDO;
    }

    @Override
    public ResultDO<Integer> executeDetailRedundancyDTO(DetailRedundancyDTO detailRedundancyDTO) {
        ResultDO<Integer> resultDO = new ResultDO<>();
        try {
            AclHelper.validateDetailRedundancyDTO(detailRedundancyDTO);

            if (EnumHelper.DELETE.Y.name().equals(detailRedundancyDTO.getIsDelete())) {
                // 删除的处理逻辑，如果detail表，存在type_name，则不允许删除，需要提前删除detail表中的type_name记录
                QueryAclResourceUserBinding queryAclResourceUserBinding = new QueryAclResourceUserBinding();
                queryAclResourceUserBinding.setBusinessLine(detailRedundancyDTO.getBusinessLine());
                queryAclResourceUserBinding.setTypeName(detailRedundancyDTO.getTypeName());
                queryAclResourceUserBinding.setDetailName(detailRedundancyDTO.getOldDetailName());
                Integer count = aclResourceUserBindingService.countAclResourceUserBindingList(queryAclResourceUserBinding);
                if (count > 0) {
                    throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_RECORD_EXIST, "有未删除的绑定信息。请提前删除【绑定表】中typeName=" + detailRedundancyDTO.getOldDetailName() + "信息。遗留记录数： " + count);
                }
                AclResourceDetailDO aclResourceDetailDO = getAclResourceDetailDO(detailRedundancyDTO);
                AclResourceDetailDO deleteAclResourceDetailDO = new AclResourceDetailDO();
                deleteAclResourceDetailDO.setAclResourceDetailId(aclResourceDetailDO.getAclResourceTypeId());
                deleteAclResourceDetailDO.setIsDelete(EnumHelper.DELETE.Y.name());
                aclResourceDetailService.updateAclResourceDetailByAclResourceDetailId(deleteAclResourceDetailDO);
                resultDO.setDomain(1);
            } else {
                AclResourceDetailDO aclResourceDetailDO = getAclResourceDetailDO(detailRedundancyDTO);
                // 根据DetailName获取要修改的Binding信息。
                QueryAclResourceUserBinding queryAclResourceUserBinding = getQueryAclResourceUserBinding(detailRedundancyDTO);
                List<AclResourceUserBindingDO> selectAclResourceUserBindingList = aclResourceUserBindingService.selectAclResourceUserBindingList(queryAclResourceUserBinding);
                List<AclResourceUserBindingDO> updateBindingList = new ArrayList<>();
                for (AclResourceUserBindingDO aclResourceUserBindingDO : selectAclResourceUserBindingList) {
                    AclResourceUserBindingDO updateBindingDO = new AclResourceUserBindingDO();
                    AclHelper.copyPropertiesIfNotNull(updateBindingDO, detailRedundancyDTO);
                    updateBindingDO.setAclResourceUserBindingId(aclResourceUserBindingDO.getAclResourceUserBindingId());
                    updateBindingDO.setAclResourceTypeId(aclResourceDetailDO.getAclResourceTypeId()); // 默认更新外键
                    updateBindingDO.setAclResourceDetailId(aclResourceDetailDO.getAclResourceDetailId()); // 默认更新外键
                    updateBindingDO.setDetailName(detailRedundancyDTO.getNewDetailName());
                    updateBindingList.add(updateBindingDO);
                }

                // 更新Binding，数据库操作，暂未加事务。
                for (AclResourceUserBindingDO updateBindingDO : updateBindingList) {
                    aclResourceUserBindingService.updateAclResourceUserBindingByAclResourceUserBindingId(updateBindingDO);
                }

                // 更新Detail
                AclResourceDetailDO updateDetailDO = new AclResourceDetailDO();
                AclHelper.copyPropertiesIfNotNull(updateDetailDO, detailRedundancyDTO);
                updateDetailDO.setAclResourceDetailId(aclResourceDetailDO.getAclResourceDetailId());
                updateDetailDO.setDetailName(detailRedundancyDTO.getNewDetailName());
                aclResourceDetailService.updateAclResourceDetailByAclResourceDetailId(updateDetailDO);

                resultDO.setDomain(updateBindingList.size() + 1);
            }

        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            resultDO = ResultHelper.buildResultDOByException(e);
        }
        return resultDO;
    }

    private static QueryAclResourceUserBinding getQueryAclResourceUserBinding(DetailRedundancyDTO detailRedundancyDTO) {
        QueryAclResourceUserBinding queryAclResourceUserBinding = new QueryAclResourceUserBinding();
        queryAclResourceUserBinding.setBusinessLine(detailRedundancyDTO.getBusinessLine());
        queryAclResourceUserBinding.setTypeName(detailRedundancyDTO.getTypeName());
        queryAclResourceUserBinding.setDetailName(detailRedundancyDTO.getOldDetailName());
        queryAclResourceUserBinding.setAllRecords();
        queryAclResourceUserBinding.setSpecifiedAttributeSet(new HashSet<String>() {{
            add("acl_resource_user_binding_id");
        }});
        return queryAclResourceUserBinding;
    }

    @Override
    public ResultDO<List<AclResourceDetailDTO>> selectAclResourceDetailList(QueryAclResourceDetailRequest queryAclResourceDetailRequest) {
        ResultDO<List<AclResourceDetailDTO>> resultDO = new ResultDO<>();
        try {
            ValidationHelper.validateWithThrow(QueryAclResourceDetailRequest.class, queryAclResourceDetailRequest);
            QueryAclResourceDetail queryAclResourceDetail = new QueryAclResourceDetail();
            BeanUtils.copyProperties(queryAclResourceDetail, queryAclResourceDetailRequest);
            ResultDO<List<AclResourceDetailDO>> selectResult = aclResourceDetailService.selectAclResourceDetailListResult(queryAclResourceDetail);
            if (selectResult.isSuccess()) {
                List<AclResourceDetailDTO> list = new ArrayList<>();
                resultDO.setDomain(list);
                for (AclResourceDetailDO aclResourceDetailDO : selectResult.getDomain()) {
                    AclResourceDetailDTO aclResourceDetailDTO = new AclResourceDetailDTO();
                    BeanUtils.copyProperties(aclResourceDetailDTO, aclResourceDetailDO);
                    list.add(aclResourceDetailDTO);
                }
                resultDO.setTotalCount(selectResult.getTotalCount());
            } else {
                BeanUtils.copyProperties(resultDO, selectResult);
            }
        } catch (Exception e) {
            logger.error(ServiceExceptionHelper.getExceptionInfo(e));
            resultDO = ResultHelper.buildResultDOByException(e);
        }
        return resultDO;
    }

    private AclResourceDetailDO getAclResourceDetailDO(DetailRedundancyDTO detailRedundancyDTO) {
        QueryAclResourceDetail queryAclResourceDetail = new QueryAclResourceDetail();
        queryAclResourceDetail.setFirstRecord();
        queryAclResourceDetail.setBusinessLine(detailRedundancyDTO.getBusinessLine());
        queryAclResourceDetail.setTypeName(detailRedundancyDTO.getTypeName());
        queryAclResourceDetail.setDetailName(detailRedundancyDTO.getOldDetailName());
        return aclResourceDetailService.selectOneAclResourceDetail(queryAclResourceDetail);
    }



    /**
     * (1)数据依赖关系校验，ResourceDetail依赖 ResourceType,根据businessLine、typeName校验ResourceType是否存在。提交批次中的businessLine、typeName在acl_resource_type表都应该存在。
     * (2)根据ResourceType信息，为AclResourceDetailDTO设置resourceTypeId。
     */
    private void setAclResourceTypeIdAndCheckTypeNameExist(List<String> typeNameList, List<String> errorList, AclResourceDetailBatchDTO aclResourceDetailBatchDTO) {
        // 数据依赖关系开始，ResourceDetail依赖 ResourceType,根据businessLine、typeName校验ResourceType是否存在。提交批次中的businessLine、typeName在acl_resource_type表都应该存在。
        QueryAclResourceType queryAclResourceType = new QueryAclResourceType();
        queryAclResourceType.setBusinessLine(aclResourceDetailBatchDTO.getOperatorDTO().getBusinessLine());
        queryAclResourceType.setAllRecords();
        queryAclResourceType.setTypeNameList(typeNameList);
        queryAclResourceType.setSpecifiedAttributeSet(queryTypeNameSet);
        List<AclResourceTypeDO> selectAclResourceTypeDOListFromDB = aclResourceTypeService.selectAclResourceTypeList(queryAclResourceType);

        Map<String, AclResourceTypeDO> aclResourceTypeDOMapBuildBySelectAclResourceTypeDOListFromDB = new HashMap<>();
        List<String> selectAclTypeNameList = new ArrayList<>();
        for (AclResourceTypeDO aclResourceTypeDO : selectAclResourceTypeDOListFromDB) {
            selectAclTypeNameList.add(aclResourceTypeDO.getTypeName());
            aclResourceTypeDOMapBuildBySelectAclResourceTypeDOListFromDB.put(aclResourceTypeDO.getTypeName(), aclResourceTypeDO);
        }

        List<String> difference = typeNameList.stream()
                .filter(item -> !selectAclTypeNameList.contains(item)) // 筛选出不在 typeNameList 中的元素
                .collect(Collectors.toList()); // 将结果收集到一个新的 List 中
        if (!difference.isEmpty()) {
            errorList.add("依赖的资源类型不存在，不存在的typeName列表" + String.join(",", difference));
            throw ServiceExceptionHelper.buildServiceException(ResultMessageEnum.DATA_LOSE_EFFICACY, errorList);
        }

        // 根据ResourceType信息，为AclResourceDetailDTO设置resourceTypeId。
        for (AclResourceDetailDTO aclResourceDetailDTO : aclResourceDetailBatchDTO.getAclResourceDetailDTOList()) {
            aclResourceDetailDTO.setAclResourceTypeId(aclResourceTypeDOMapBuildBySelectAclResourceTypeDOListFromDB.get(aclResourceDetailDTO.getTypeName()).getAclResourceTypeId());
        }
        // 数据依赖关系结束
    }

}
